import { NodeEdge } from "./NodeEdge";
import { NodeRoot } from "./NodeRoot";
import { ParamNode, ParamObject } from "./ParamNode";

export class NodeGraph {

    private _node: cc.Node;
    public get node(): cc.Node {
        return this._node;
    }
    public set node(v: cc.Node) {
        this._node = v;
    }
    private nodeMap = new Map<string, NodeRoot>();
    private edgeMap = new Map<string, NodeEdge>();
    private nodeInputEdgeMap = new Map<string, Array<NodeEdge>>();
    private nodeOutputEdgeMap = new Map<string, Array<NodeEdge>>();
    private valueMap = new Map<string, ParamObject>();
    private updateNodes = new Array<NodeRoot>();
    private exitNodes = new Array<NodeRoot>();
    constructor(jsonObj, _node: cc.Node) {
        this.node = _node;
        this.init(jsonObj["MonoBehaviour"]);
    }
    public start() {
        let a = Array.from(this.nodeMap.values());
        for (const node of a) {
            if (node instanceof NodeClassMap["EntryNode"]) {
                this.updateNodes.push(node);
                node.onEnter();
                break;
            }
        }
        console.log(this);
    }
    public update(dt: number) {
        this.exitNodes.length = 0;
        for (const updateNode of this.updateNodes) {
            if (updateNode.onStep(dt)) {
                this.exitNodes.push(updateNode);
            };
        }
        //let nextNodeEdges = new Array<NodeEdge>();
        for (const exitNode of this.exitNodes) {
            this.bFSOrder(exitNode, dt);
            // this.updateNodes.splice(this.updateNodes.indexOf(exitNode), 1);
            // exitNode.onExit();
            // let outEdges = this.getOutEdges(exitNode);
            // if (outEdges) {
            //     for (const outEdge of outEdges) {
            //         if (outEdge.outputFieldName != "output") {
            //             let outputNode = this.nodeMap.get(outEdge.inputNodeGUID);
            //             this.initOutputNodeparam(outputNode);
            //         } else {
            //             nextNodeEdges.push(outEdge);
            //         }
            //     }
            // }
        }
        // for (const nextNodeEdge of nextNodeEdges) {
        //     let nextNode = this.nodeMap.get(nextNodeEdge.inputNodeGUID);
        //     this.initOutputNodeparam(nextNode);
        // }
        // for (const nextNodeEdge of nextNodeEdges) {
        //     let nextNode = this.nodeMap.get(nextNodeEdge.inputNodeGUID);
        //     this.updateNodes.push(nextNode);
        //     nextNode.onEnter();
        // }
    }
    public lateUpdate() {
        for (const updateNode of this.updateNodes) {
            updateNode.onLateUpdate();
        }
    }
    public removeUpdateNode(node: NodeRoot) {
        this.updateNodes.splice(this.updateNodes.indexOf(node), 1);
    }
    private bFSOrder(rootNode: NodeRoot, dt: number) {
        //let nextNodeEdges = new Array<NodeEdge>();


        //let outEdges = this.getOutEdges(nextNode);
        // if (outEdges) {
        //     for (const outEdge of outEdges) {
        //         if (outEdge.outputFieldName != "output") {
        //             let outputNode = this.nodeMap.get(outEdge.inputNodeGUID);
        //             this.initOutputVar(outputNode);
        //         } else {
        //             nextNodeEdges.push(outEdge);
        //         }
        //     }
        // }
        // for (const nextNodeEdge of nextNodeEdges) {
        //     let nextNode = this.nodeMap.get(nextNodeEdge.inputNodeGUID);
        //     this.initOutputVar(nextNode);
        // }
        let nextNodeQueue = new Array<NodeRoot>();
        nextNodeQueue.push(rootNode);
        while (nextNodeQueue.length > 0) {
            let tempNode = nextNodeQueue.shift();
            console.log(tempNode);
            if (tempNode != rootNode) {
                this.initInputVar(tempNode);
                tempNode.onEnter();
                this.updateNodes.push(tempNode);
                if (tempNode.onStep(dt)) {
                    this.updateNodes.splice(this.updateNodes.indexOf(tempNode), 1);
                    tempNode.onExit();
                    let outEdges = this.getOutEdges(tempNode);
                    if (outEdges) {
                        this.initOutputVar(tempNode);
                        for (const outEdge of outEdges) {
                            if (outEdge.outputFieldName == "output") {
                                let outNode = this.getNode(outEdge.inputNodeGUID);
                                if (!(outNode instanceof ParamNode))
                                    nextNodeQueue.push(outNode);
                            }
                        }
                    }
                }
            }
            else {
                this.updateNodes.splice(this.updateNodes.indexOf(tempNode), 1);
                tempNode.onExit();
                let outEdges = this.getOutEdges(tempNode);
                if (outEdges) {
                    this.initOutputVar(tempNode);
                    for (const outEdge of outEdges) {
                        if (outEdge.outputFieldName == "output") {
                            let outNode = this.getNode(outEdge.inputNodeGUID);
                            if (!(outNode instanceof ParamNode))
                                nextNodeQueue.push(outNode);
                        }
                    }
                }
            }

        }


        // for (const nextNodeEdge of nextNodeEdges) {
        //     let nextNode = this.nodeMap.get(nextNodeEdge.inputNodeGUID);
        //     this.updateNodes.push(nextNode);
        //     nextNodeQueue.push(nextNode);
        //     nextNode.onEnter();
        //     if(nextNode.onStep(dt)){
        //         this.toNextNode(nextNode,dt);
        //     }
        // }
    }
    public getNode(guid: string) {
        return this.nodeMap.get(guid);
    }
    public getInputEdges(node: NodeRoot) {
        let inputEdges = this.nodeInputEdgeMap.get(node.GUID);
        if (inputEdges && inputEdges.length > 0) {
            return inputEdges;
        }
        else {
            return null;
        }
    }
    public getOutEdges(node: NodeRoot) {
        let outEdges = this.nodeOutputEdgeMap.get(node.GUID);
        if (outEdges && outEdges.length > 0) {
            return outEdges;
        }
        else {
            return null;
        }
    }
    public getInputNodes(node: NodeRoot) {
        let inputEdges = this.nodeInputEdgeMap.get(node.GUID);
        let inputNodes = new Array<NodeRoot>();
        if (inputEdges && inputEdges.length > 0) {
            for (const inputEdge of inputEdges) {
                inputNodes.push(this.nodeMap.get(inputEdge.outputNodeGUID));
            }
            return inputNodes;
        }
        else {
            return null;
        }
    }
    public getOutNodes(node: NodeRoot) {
        let outEdges = this.nodeOutputEdgeMap.get(node.GUID);
        let outNodes = new Array<NodeRoot>();
        if (outEdges && outEdges.length > 0) {
            for (const outEdge of outEdges) {
                outNodes.push(this.nodeMap.get(outEdge.inputNodeGUID));
            }
            return outNodes;
        }
        else {
            return null;
        }
    }
    public getParamObject(key: string) {
        let paramObj = this.valueMap.get(key);
        return paramObj;
    }
    public setParamObject(key: string, paramObject: ParamObject) {
        this.valueMap.set(key, paramObject);
    }
    public removeValue(key: string) {
        this.valueMap.delete(key);
    }
    private init(jsonObj) {
        for (const node of jsonObj["serializedNodes"]) {
            this.initNode(node);
        }
        for (const edge of jsonObj["edges"]) {
            this.initEdge(edge);
        }
        for (const param of jsonObj["exposedParameters"]) {
            this.initParam(param);
        }
        this.nodeMap.forEach(node => {
            node.onInit();
        });
    }
    private initNode(jsonObj) {
        let typeStr = jsonObj["type"].split(',')[0];
        let type;
        if (typeStr == "GraphProcessor.ParameterNode") {
            type = ParamNode;
        }
        else {
            type = NodeClassMap[typeStr];
        }
        console.log(type, typeStr);
        let node = JSON.parse(jsonObj["jsonDatas"]);
        Object.setPrototypeOf(node, type.prototype);
        this.nodeMap.set(node.GUID, node);
        node.parent = this;
    }
    private initEdge(jsonObj) {
        let edge = Object.setPrototypeOf(jsonObj, NodeEdge.prototype) as NodeEdge;
        this.edgeMap.set(edge.GUID, edge);

        let inputNodeEdges = this.nodeInputEdgeMap.get(edge.inputNodeGUID);
        if (!inputNodeEdges) {
            inputNodeEdges = new Array<NodeEdge>();
            this.nodeInputEdgeMap.set(edge.inputNodeGUID, inputNodeEdges);
        }
        inputNodeEdges.push(edge);

        let outNodeEdges = this.nodeOutputEdgeMap.get(edge.outputNodeGUID);
        if (!outNodeEdges) {
            outNodeEdges = new Array<NodeEdge>();
            this.nodeOutputEdgeMap.set(edge.outputNodeGUID, outNodeEdges);
        }
        outNodeEdges.push(edge);
    }
    private initParam(jsonObj) {
        Object.setPrototypeOf(jsonObj, ParamObject.prototype);
        this.setParamObject(jsonObj.guid, jsonObj);
    }
    private initOutputVar(node: NodeRoot) {
        let outEdges = this.getOutEdges(node);
        for (const outEdge of outEdges) {
            if (outEdge.outputFieldName != "output") {
                let inputNode = node;
                let outputNode = this.nodeMap.get(outEdge.inputNodeGUID);
                if (outputNode instanceof ParamNode) {
                    outputNode.setValue(inputNode[outEdge.outputFieldName]);
                }
                else {
                    outputNode[outEdge.inputFieldName] = inputNode[outEdge.outputFieldName];
                }
            }
        }
    }
    private initInputVar(node: NodeRoot) {
        let inputEdges = this.getInputEdges(node);
        if (!inputEdges) {
            return;
        }
        for (const inputEdge of inputEdges) {
            if (inputEdge.inputNodeGUID != "input") {
                let inputNode = this.nodeMap.get(inputEdge.outputNodeGUID);
                let outputNode = node;
                console.log(inputNode,outputNode);
                if (inputNode instanceof ParamNode) {
                    outputNode[inputEdge.inputFieldName] = inputNode.getValue();
                }
                else {
                    outputNode[inputEdge.inputFieldName] = inputNode[inputEdge.outputFieldName];
                }
            }
        }
    }

}
const NodeClassMap = {};
export function NMNode(con) {
    NodeClassMap[con.name] = con;
}
